﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using BobcatUtility;
using System.Runtime.Serialization;

namespace CNShapes
{
    /// <summary>
    /// Base class for the different shape classes that can be drawn and shown in
    /// the collaborative-notepad application.
    /// </summary><remarks>
    /// All shapes have:
    /// - A unique string ID
    /// - The shape's rectangular bounds (to help with rendering)
    /// - A version number. This will be updated if changes are made to the shape. 
    ///   Again this is to help with rendering.
    /// </remarks>
    [DataContract]
    public abstract class Shape
    {
        #region Events

        /// <summary>
        /// Args raised with events from this class.
        /// </summary>
        public class Args : EventArgs
        {
        }

        /// <summary>
        /// Event raised when the shape has been edited.
        /// </summary>
        public event EventHandler<Args> ShapeEditedEvent;

        /// <summary>
        /// Function to be able to raise the event from derived classes.
        /// </summary>
        protected void raiseShapeEditedEvent()
        {
            Utility.CrossThreadInvoke(ShapeEditedEvent, this, getArgs());
        }

        /// <summary>
        /// Event raised when editing has finished on a shape. (Usually raised
        /// when an input action has completed.)
        /// </summary>
        public event EventHandler<Args> ShapeEditCompletedEvent;

        /// <summary>
        /// Function to raise the edit-completed event from derived classes.
        /// </summary>
        protected void raiseShapeEditCompletedEvent()
        {
            Utility.CrossThreadInvoke(ShapeEditCompletedEvent, this, getArgs());
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Property to get the ID for this shape.
        /// </summary>
        public Guid ID 
        {
            get { return m_ID; }
        }

        /// <summary>
        /// The rectangle containing this shape.
        /// </summary>
        public RectangleF Bounds 
        { 
            get { return m_Bounds; } 
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public Shape()
        {
            m_ID = Guid.NewGuid();
        }

        /// <summary>
        /// Called (for example by an editing action) when editing a shape has completed.
        /// </summary>
        public void editCompleted()
        {
            raiseShapeEditCompletedEvent();
        }

        #endregion

        #region Protected methods

        /// <summary>
        /// We update the bounds from the points passed in.
        /// </summary>
        protected void updateBounds(PointF pt1, PointF pt2)
        {
            m_bBoundsInitialized = false;
            updateBounds(pt1);
            updateBounds(pt2);
        }
        
        /// <summary>
        /// Updates the bounds to include the point passed in.
        /// </summary>
        protected void updateBounds(PointF point)
        {
            // Have any bounds been set yet? If not, the bounds are just the new point...
            if (m_bBoundsInitialized == false)
            {
                m_Bounds.X = point.X;
                m_Bounds.Y = point.Y;
                m_Bounds.Width = 0;
                m_Bounds.Height = 0;
                m_bBoundsInitialized = true;
                return;
            }

            // There are already existing bounds. We check if the point falls outside
            // them, and we update the bounds if it does...
            float boundsLeft = m_Bounds.Left;
            float boundsTop = m_Bounds.Top;
            float boundsRight = m_Bounds.Right;
            float boundsBottom = m_Bounds.Bottom;
            if (point.X < boundsLeft)
            {
                boundsLeft = point.X;
            }
            if (point.X > boundsRight)
            {
                boundsRight = point.X;
            }
            if (point.Y < boundsTop)
            {
                boundsTop = point.Y;
            }
            if (point.Y > boundsBottom)
            {
                boundsBottom = point.Y;
            }
            m_Bounds.X = boundsLeft;
            m_Bounds.Y = boundsTop;
            m_Bounds.Width = boundsRight - boundsLeft;
            m_Bounds.Height = boundsBottom - boundsTop;
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Creates a new ShapeArgs object that can be passed with events.
        /// </summary>
        private Args getArgs()
        {
            return new Args();
        }

        #endregion

        #region Private data

        // Unique ID for this shape...
        [DataMember]
        private Guid m_ID;

        [DataMember]
        private RectangleF m_Bounds = new RectangleF();

        [DataMember]
        private bool m_bBoundsInitialized = false;

        #endregion
    }
}
